package com.why.serializable;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

import com.why.enums.SerializerCode;
import com.why.message.RpcRequest;
import com.why.message.RpcResponse;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;

/**
 * @program: whyrpcframework
 * @description:
 * @author: @why
 * @create: 2021-03-13 11:40
 **/
@Slf4j
public class KroSerializer implements CommonSerializable {


    private static final ThreadLocal<Kryo> kroThreadLocal = ThreadLocal.withInitial(
            () -> {
//注册会给每一个class一个int类型的Id相关联，这显然比类名称高效，但同时要求反序列化的时候的Id必须与序列化过程中一致。
// 这意味着注册的顺序非常重要。
//但是由于现实原因，同样的代码，同样的Class在不同的机器上注册编号任然不能保证一致，所以多机器部署时候反序列化可能会出现问题。
//
//所以kryo默认会禁止类注册，当然如果想要打开这个属性，可以通过kryo.setRegistrationRequired(true);打开。
//                循环引用
//                这是对循环引用的支持，可以有效防止栈内存溢出，
//                kryo默认会打开这个属性。当你确定不会有循环引用发生的时候，
//                可以通过kryo.setReferences(false);关闭循环引用检测，从而提高一些性能。

                Kryo kryo = new Kryo();
                kryo.register(RpcResponse.class);
                kryo.register(RpcRequest.class);
                kryo.setReferences(true);
                kryo.setRegistrationRequired(false);
                return kryo;
            });

    /**
     * 将对象序列化
     * @param object
     * @return
     */
    @Override
    public byte[] serialize(Object object) {

        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
             Output output = new Output(outputStream)) {
            System.out.println("kryo");
            Kryo kryo = kroThreadLocal.get();
            kryo.writeObject(output, object);
            kroThreadLocal.remove();

            return output.toBytes();
        } catch (Exception e) {
            throw new RuntimeException("Kryo序列化过程发生异常");
        }

    }

    /**
     * 对象反序列化
     * 当一个请求使用完某个线程，该线程会放回线程池被其它请求使用，这就导致一个问题，
     * 不同的请求还是有可能会使用到同一个线程（只要请求数量大于线程数量），而ThreadLocal是属于线程的，
     *
     * 如果我们使用完ThreadLocal对象而没有手动删掉，
     * 那么后面的请求就有机会使用到被使用过的ThreadLocal对象！
     * @param bytes
     * @param clazz
     * @return
     */

    @Override
    public Object deserialize(byte[] bytes, Class<?> clazz) {
        try (ByteArrayInputStream in = new ByteArrayInputStream(bytes);
        Input input = new Input(in)) {
            System.out.println("kryo");
            Kryo kryo = kroThreadLocal.get();
            Object readObject = kryo.readObject(input, clazz);
            kroThreadLocal.remove();
            return  readObject;
        } catch (Exception e) {
            throw new RuntimeException("Kryo反序列化过程发生异常");
        }
    }

    @Override
    public int getCode() {
        return SerializerCode.valueOf("KRYO").getCode();
    }
}
